package com.ben.multiple.shiro;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.BearerToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @author BenJin Yin
 * @date 2021/2/1
 */
@Slf4j
public class MyAuthenticationFilter extends AuthenticationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {
        String token = ((HttpServletRequest) request).getHeader("token");
        // 没有自定义请求头，直接返回
        if (!StringUtils.hasText(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            Map<String, Object> map = new HashMap<>();
            map.put("code", 500);
            map.put("msg", "未认证，请先登录！");
            try {
                String json = new ObjectMapper().writeValueAsString(map);
                responseJsonWriter((HttpServletResponse) response, (HttpServletRequest) request, json);
                return false;
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        AuthenticationToken authenticationToken = new BearerToken(token, request.getRemoteHost());
        Subject subject = getSubject(request, response);
        try {
            // 所有没放行的请求，都会执行此逻辑。
            // 实际是通过自定义的 CustomRealm 获取用户信息，并将用户信息保存到 当前的 Subject 中。
            subject.login(authenticationToken);
            return true;
        } catch (AuthenticationException e) {
            log.error(e.getMessage(), e);
            // 从缓存中获取用户信息失败，直接返回。
            Map<String, Object> map = new HashMap<>();
            map.put("code", 500);
            map.put("msg", "认证失败，请重新登录！");
            try {
                String json = new ObjectMapper().writeValueAsString(map);
                responseJsonWriter((HttpServletResponse) response, (HttpServletRequest) request, json);
            } catch (IOException ioException) {
                log.error(e.getMessage(), ioException);
            }
        }
        return false;
    }

    public static void responseJsonWriter(HttpServletResponse response, HttpServletRequest request, String json) {
        if (response.isCommitted()) {
            return;
        }
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        PrintWriter printWriter;
        try {
            printWriter = response.getWriter();
        } catch (IOException e) {
            log.error("获取输出字符流失败", e);
            return;
        }
        printWriter.print(json);
        printWriter.flush();
        printWriter.close();
    }
}
